package com.gitee.wsl.time

import com.gitee.wsl.ext.math.addExact
import com.gitee.wsl.ext.math.floorDiv
import com.gitee.wsl.ext.math.floorMod
import com.gitee.wsl.math.Math
import kotlinx.datetime.*
import kotlin.math.round
import kotlin.time.Instant
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.microseconds
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
import kotlin.time.ExperimentalTime
import kotlin.time.toDuration


internal val DURATION_NIL: Duration = (-0x001FFFFFFFFFFFF3L).toDuration(DurationUnit.NANOSECONDS)

/** [TimeSpan] representing this number as [milliseconds] or 1 / 1_000 [seconds]. */
inline val Double.milliseconds: Duration get() = toDuration(DurationUnit.MILLISECONDS)

val Duration.Companion.NIL: Duration get() = DURATION_NIL
//val Duration.Companion.ZERO get() = Duration.ZERO

val Duration.Companion.Short get() = 4.seconds
val Duration.Companion.Medium get() = 7.seconds
val Duration.Companion.Long get() = 10.seconds

val Duration.Companion.MIN_DURATION: Duration
    get() = Duration.fromNanoseconds(Double.MIN_VALUE)

val Duration.Companion.MAX_DURATION: Duration
    get() = Duration.fromNanoseconds(Double.MAX_VALUE)

/** [TimeSpan] representing this number as [weeks] or 604_800 [seconds]. */
inline val Long.weeks: Duration get() = (this * 7).days

inline val Float.weeks get() = this.toDouble().weeks

inline val Int.weeks: Duration get() = this.toDouble().weeks

inline val Double.weeks: Duration get() = (this * 7).days

fun Duration.Companion.ofMillis(millis: Int) = millis.milliseconds

fun Duration.isPreciseToNanosecond(): Boolean {
    return this == inWholeNanoseconds.nanoseconds
}

val Duration.sign: Int get() = when {
    isPositive() -> 1
    isNegative() -> -1
    else -> 0
}

fun Duration.Companion.fromNanoseconds(value: Double): Duration = value.nanoseconds
fun Duration.Companion.fromMicroseconds(value: Double): Duration = value.microseconds
fun Duration.Companion.fromMilliseconds(value: Double): Duration = value.milliseconds
fun Duration.Companion.fromSeconds(value: Double): Duration = value.seconds
fun Duration.Companion.fromMinutes(value: Double): Duration = value.minutes
fun Duration.Companion.fromHours(value: Double): Duration = value.hours
fun Duration.Companion.fromDays(value: Double): Duration = value.days
fun Duration.Companion.fromWeeks(value: Double): Duration = value.weeks

fun Duration.Companion.fromNanoseconds(value: Number): Duration = value.toDouble().nanoseconds
fun Duration.Companion.fromMicroseconds(value: Number): Duration = value.toDouble().microseconds
fun Duration.Companion.fromMilliseconds(value: Number): Duration = value.toDouble().milliseconds
fun Duration.Companion.fromSeconds(value: Number): Duration = value.toDouble().seconds
fun Duration.Companion.fromMinutes(value: Number): Duration = value.toDouble().minutes
fun Duration.Companion.fromHours(value: Number): Duration = value.toDouble().hours
fun Duration.Companion.fromDays(value: Number): Duration = value.toDouble().days
fun Duration.Companion.fromWeeks(value: Number): Duration =value.toDouble().weeks

val Duration.offset:UtcOffset get() = UtcOffset(hours = this.inWholeHours.toInt(),minutes = this.inWholeMinutes.toInt(),seconds = this.inWholeSeconds.toInt())

fun Duration.removeFraction(minUnit: DurationUnit): Duration {
    return when(minUnit) {
        DurationUnit.NANOSECONDS -> this.inWholeNanoseconds.nanoseconds
        DurationUnit.MICROSECONDS -> this.inWholeMicroseconds.microseconds
        DurationUnit.MILLISECONDS -> this.inWholeMilliseconds.milliseconds
        DurationUnit.SECONDS -> this.inWholeSeconds.seconds
        DurationUnit.MINUTES -> this.inWholeMinutes.minutes
        DurationUnit.HOURS -> this.inWholeHours.hours
        DurationUnit.DAYS -> this.inWholeDays.days
        else -> this.inWholeSeconds.seconds
    }
}

fun Duration.Companion.between(start: LocalDateTime, end: LocalDateTime): Duration  = end - start

fun Duration.Companion.between(start: LocalDate, end: LocalDate): Duration  = end - start

@OptIn(ExperimentalTime::class)
fun Duration.Companion.between(start: Instant, end: Instant): Duration  = end - start


const val nanosecondsPerSecond = 1_000_000_000L
const val nanosecondsPerMinute = nanosecondsPerSecond * 60
const val nanosecondsPerHour = nanosecondsPerMinute * 60
const val nanosecondsPerDay = nanosecondsPerHour * 24

const val MILLIS_PER_SECOND = 1000
const val MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60 // 60_000
const val MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60 // 3600_000
const val MILLIS_PER_DAY = MILLIS_PER_HOUR * 24 // 86400_000
const val MILLIS_PER_WEEK = MILLIS_PER_DAY * 7 // 604800_000
const val MILLIS_PER_MICROSECOND = 1.0 / 1000.0
const val MILLIS_PER_NANOSECOND = MILLIS_PER_MICROSECOND / 1000.0


fun Duration.monStr() = toComponents { days, hours, minutes, _, _ ->
    when {
        days > 365  -> "${days / 365}年前"
        days > 30   -> "${days / 30}个月前"
        days > 7    -> "${days / 7}周前"
        days > 0    -> "${days}天前"
        hours > 0   -> "${hours}小时前"
        minutes > 0 -> "${minutes}分钟前"
        else        -> "刚刚"
    }
}

// TODO Doesn't check for overflows. Improve.
fun Duration(
    days: Int = 0,
    hours: Int = 0,
    minutes: Int = 0,
    seconds: Int = 0,
    nanoseconds: Int = 0,
): Duration = (
        (days * nanosecondsPerDay) +
                (hours * nanosecondsPerHour) +
                (minutes * nanosecondsPerMinute) +
                (seconds * nanosecondsPerSecond) +
                nanoseconds
        ).nanoseconds

fun Duration.getPrettyDuration(
    secondsLabel: String,
    minutesLabel: String,
    hoursLabel: String,
): String =
    when {
        inWholeHours > 0 ->
            buildString {
                append(inWholeHours)
                append(hoursLabel)
                val remainderMinutes = inWholeMinutes % 60
                val remainderSeconds = inWholeSeconds % 60
                if (remainderMinutes > 0 || remainderSeconds > 0) {
                    append(" ")
                    append(remainderMinutes)
                    append(minutesLabel)
                }
                if (remainderSeconds > 0) {
                    append(" ")
                    append(remainderSeconds)
                    append(secondsLabel)
                }
            }

        inWholeMinutes > 0 ->
            buildString {
                append(inWholeMinutes)
                append(minutesLabel)
                val remainderSeconds = inWholeSeconds % 60
                if (remainderSeconds > 0) {
                    append(" ")
                    append(remainderSeconds)
                    append(secondsLabel)
                }
            }

        else ->
            buildString {
                val rounded = round((inWholeMilliseconds / 1000.0) * 10.0) / 10.0
                if (rounded % 1 <= 0) {
                    append(rounded.toInt())
                } else {
                    append(rounded)
                }
                append(secondsLabel)
            }
    }

fun Duration.compensateInaccuracy(): Duration =
    when {
        this <= 2.hours -> this * 0.85
        this <= 8.hours -> this * 0.95
        else -> this
    }


fun Duration.coerceAtLeastZero(): Duration =
    if (isNegative()) Duration.ZERO else this

 fun Duration.toHoursPart(): Int {
    return (toLong(DurationUnit.HOURS) % 24).toInt()
}

 fun Duration.toMinutesPart(): Int {
    return (toLong(DurationUnit.MINUTES) % 60).toInt()
}

 fun Duration.toSecondsPart(): Int {
    return (toLong(DurationUnit.SECONDS) % 60).toInt()
}

 fun Duration.toMillisPart(): Int {
    return (toLong(DurationUnit.MILLISECONDS) % 1_000).toInt()
}

fun Duration.toNanosPart(): Int {
    return (toLong(DurationUnit.NANOSECONDS) % 1_000_000_000).toInt()
}

fun Duration.negated() = this * -1


fun Duration.Companion.ofSeconds(value: Int) = value.seconds
fun Duration.Companion.ofNanos(value: Int) = value.nanoseconds

fun Duration.toNanos() = inWholeNanoseconds

fun Duration.Companion.ofNanoseconds(value: Number): Duration = value.toDouble().nanoseconds
fun Duration.Companion.ofMicroseconds(value: Number): Duration = value.toDouble().microseconds
fun Duration.Companion.ofMilliseconds(value: Number): Duration = value.toDouble().milliseconds
fun Duration.Companion.ofSeconds(value: Number): Duration = value.toDouble().seconds
fun Duration.Companion.ofMinutes(value: Number): Duration = value.toDouble().minutes
fun Duration.Companion.ofHours(value: Number): Duration = value.toDouble().hours
fun Duration.Companion.ofDays(value: Number): Duration = value.toDouble().days
fun Duration.Companion.ofWeeks(value: Number): Duration =value.toDouble().weeks


/**
 * Obtains a `Duration` representing a number of seconds and an
 * adjustment in nanoseconds.
 *
 *
 * This method allows an arbitrary number of nanoseconds to be passed in.
 * The factory will alter the values of the second and nanosecond in order
 * to ensure that the stored nanosecond is in the range 0 to 999,999,999.
 * For example, the following will result in exactly the same duration:
 * <pre>
 * Duration.ofSeconds(3, 1);
 * Duration.ofSeconds(4, -999_999_999);
 * Duration.ofSeconds(2, 1000_000_001);
</pre> *
 *
 * @param seconds  the number of seconds, positive or negative
 * @param nanoAdjustment  the nanosecond adjustment to the number of seconds, positive or negative
 * @return a `Duration`, not null
 * @throws ArithmeticException if the adjustment causes the seconds to exceed the capacity of `Duration`
 */
fun Duration.Companion.ofSeconds(seconds: Long, nanoAdjustment: Long): Duration {
    val secs: Long = Math.addExact(seconds, Math.floorDiv(nanoAdjustment, NANOS_PER_SECOND))
    val nos = Math.floorMod(nanoAdjustment, NANOS_PER_SECOND).toInt()
    return secs.milliseconds + nos.nanoseconds
}

private const val NANOS_PER_SECOND = 1000000000L